package cyce.peer.filechooser;

import cyce.file.*;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**
 * FileChooser Panel contains all of the UI for Peers
 */
public class FileChooser extends JPanel {

	private static final long serialVersionUID = -6157688513562139796L;
	private FileModel model;
	private JTree jTree;
	private JButton downloadButton = new JButton("Download File");
	private JButton deleteFileButton = new JButton("Delete File");
	private JButton deleteDirectoryButton = new JButton("Delete Directory");
	private JButton addDirectoryButton = new JButton("Add Directory");
	private JButton uploadButton = new JButton("Upload File");

	public FileChooser(FileModel model) {
		super();
		
		this.model = model;
		
		this.jTree = new JTree(this.model.getTreeModel());

		this.layoutView();
		this.registerControllers();
	}
	
	/**
	 * Set up the UI to display properly
	 */
	private void layoutView()
	{
		this.setPreferredSize(new Dimension(200, 400));
		
		this.jTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		this.jTree.setEditable(false);
		
		this.downloadButton.setEnabled(false);
		this.deleteFileButton.setEnabled(false);
		this.deleteDirectoryButton.setEnabled(false);
		this.addDirectoryButton.setEnabled(false);
		this.uploadButton.setEnabled(false);

		this.setLayout(new GridBagLayout());
		
		JScrollPane scroller = new JScrollPane(this.jTree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		
		scroller.setPreferredSize(new Dimension(200, 400));
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.fill = GridBagConstraints.BOTH;
		gbc.weightx = 1.0;
		gbc.weighty = 1.0;
		gbc.gridx = 0;
		gbc.gridy = GridBagConstraints.RELATIVE;
		this.add(scroller, gbc);
		gbc.weightx = 0.0;
		gbc.weighty = 0.0;
		this.add(Box.createVerticalStrut(10), gbc);
		this.add(this.downloadButton, gbc);
		this.add(this.deleteFileButton, gbc);
		this.add(this.deleteDirectoryButton, gbc);
		this.add(Box.createVerticalStrut(10), gbc);
		this.add(this.addDirectoryButton, gbc);
		this.add(this.uploadButton, gbc);
	}

	/**
	 * Add listeners to the UI controls
	 */
	private void registerControllers()
	{
		// Register a tree selection listener on the JTree to find out when the user selected a node
		this.jTree.addTreeSelectionListener(new TreeSelectionListener()
		{
			public void valueChanged(TreeSelectionEvent evt)
			{
				FileChooser.this.jTree_selectionChanged();
			}
		});
		
		// Register a TreeModelListener on jTree's tree model to update selection
		this.jTree.getModel().addTreeModelListener(new TreeModelListener()
		{
			public void treeNodesChanged(TreeModelEvent te) {
				FileChooser.this.jTree_selectionChanged();
			}
			public void treeStructureChanged(TreeModelEvent te) {
				FileChooser.this.jTree_selectionChanged();
			}
			public void treeNodesInserted(TreeModelEvent te) {
				FileChooser.this.jTree_selectionChanged();
			}
			public void treeNodesRemoved(TreeModelEvent te) {
				FileChooser.this.jTree_selectionChanged();
			}
		});

		// What to do when the downloadButton button is clicked.
		this.downloadButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				FileChooser.this.downloadButton_click();
			}
		});
		
		// What to do when the deleteFileButton is clicked
		this.deleteFileButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				FileChooser.this.deleteFileButton_click();
			}
		});

		// What to do when the deleteDirectoryButton is clicked
		this.deleteDirectoryButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				FileChooser.this.deleteDirectoryButton_click();
			}
		});

		// What to do when the addDirectoryButton button is clicked.
		this.addDirectoryButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				FileChooser.this.addDirectoryButton_click();
			}
		});
		
		// What to do when the uploadButton button is clicked.
		this.uploadButton.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				FileChooser.this.uploadButton_click();
			}
		});
	}


	/*
	 * Listeners
	 */

	/**
	 * Update the enabled status of buttons based on the currently
	 * selected node in the tree
	 */
	private void jTree_selectionChanged() {
		TreePath path = this.jTree.getSelectionPath();
		if (path == null)
		{
			this.downloadButton.setEnabled(false);
			this.deleteFileButton.setEnabled(false);
			this.deleteDirectoryButton.setEnabled(false);
			this.addDirectoryButton.setEnabled(false);
			this.uploadButton.setEnabled(false);
			return;
		}

		File selectedFile = ((File)path.getLastPathComponent());
		boolean dirSelected = (selectedFile instanceof Directory);
		Directory selectedDir = (dirSelected ? (Directory)selectedFile : null);
		
		// only allow downloading files
		this.downloadButton.setEnabled(!dirSelected);
		
		// allow deleting all files and all non-root directories
		this.deleteFileButton.setEnabled(!dirSelected);
		this.deleteDirectoryButton.setEnabled(
				dirSelected && !selectedDir.isRoot());
		
		// allow uploading into directories
		this.uploadButton.setEnabled(dirSelected);
		this.addDirectoryButton.setEnabled(dirSelected);

	}
	
	/**
	 * Start procedure of downloading remote file
	 * Show save dialog to choose location to download file to
	 * Show dialog to inform user that download is in progress
	 */
	private void downloadButton_click() {
		TreePath path = this.jTree.getSelectionPath();
		File selectedFile = ((File)path.getLastPathComponent());
		
		JFileChooser saveDialog = new JFileChooser();
		saveDialog.setSelectedFile(new java.io.File(selectedFile.getName()));
	    int returnVal = saveDialog.showSaveDialog(this);
	    if(returnVal == JFileChooser.APPROVE_OPTION) {
	    	String savePath = saveDialog.getSelectedFile().getAbsolutePath();

			JDialog dialog = new JDialog((JFrame)this.getTopLevelAncestor(), "Downloading", true);
			JPanel panel = new JPanel();
			panel.add(new javax.swing.JLabel("Downloading file..."));
			panel.setPreferredSize(new Dimension(150,75));
			dialog.setContentPane(panel);
			dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
			dialog.pack();
			
			Thread downloadThread = new DownloadThread(dialog, selectedFile, savePath);
			downloadThread.start();
			
			dialog.setVisible(true);
			
	    }
	}
	
	/**
	 * Worker thread to allow downloading dialog to display without 
	 * blocking the download process
	 */
	private class DownloadThread extends Thread{
		JDialog dialog;
		File selectedFile;
		String savePath;
		
		DownloadThread(JDialog dialog, File selectedFile, String savePath){
			this.dialog = dialog;
			this.selectedFile = selectedFile;
			this.savePath = savePath;
		}
		
		public void run() {
			try {
				FileChooser.this.model.saveFile(selectedFile, savePath);
				dialog.setVisible(false);
			} catch (Exception e1) {
				dialog.setVisible(false);
				JOptionPane.showMessageDialog(FileChooser.this, 
						e1.getMessage(), 
						"Could not download file.", 
						JOptionPane.ERROR_MESSAGE);
			} 
		}
	}
	
	/**
	 * Run procedure of deleting a remote file
	 * Show confirmation dialog
	 */
	private void deleteFileButton_click() {
		TreePath path = this.jTree.getSelectionPath();
		File selectedFile = ((File)path.getLastPathComponent());
		
		int result = JOptionPane.showConfirmDialog(this, 
				"Are you sure you want to delete this file?", 
				"Confirm Deletion", 
				JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
		
		if (result == JOptionPane.YES_OPTION) {
			try {
				this.model.deleteFile(selectedFile);
			}
			catch (Exception e1) {
				JOptionPane.showMessageDialog(this, 
						e1.getMessage(), 
						"Could not delete file.", 
						JOptionPane.ERROR_MESSAGE);
			} 
		}
	}
	
	/**
	 * Run procedure of deleting a remote directory
	 * Show confirmation dialog
	 */
	private void deleteDirectoryButton_click() {
		TreePath path = this.jTree.getSelectionPath();
		Directory selectedDir = ((Directory)path.getLastPathComponent());
		
		int result = JOptionPane.showConfirmDialog(this, 
				"Are you sure you want to delete this directory and all files contained within it?", 
				"Confirm Deletion", 
				JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
		
		if (result == JOptionPane.YES_OPTION) {
			try {
				this.model.deleteDirectory(selectedDir);
			}
			catch (Exception e1) {
				JOptionPane.showMessageDialog(this, 
						e1.getMessage(), 
						"Could not delete directory.", 
						JOptionPane.ERROR_MESSAGE);
			} 
		}
	}
	
	/**
	 * Run procedure of adding a directory
	 * Show a dialog for user to input directory name
	 */
	private void addDirectoryButton_click() {
		TreePath path = this.jTree.getSelectionPath();
		Directory selectedDir = ((Directory)path.getLastPathComponent());

    	String dirName = JOptionPane.showInputDialog(this, 
    			"Enter the new directory name:",
    			"Add Directory", JOptionPane.PLAIN_MESSAGE);
    	if (dirName == null) return;
    	
    	// keep asking user until they enter a valid dir name
    	while (!this.model.validateDirectoryName(dirName)) {
    		dirName = JOptionPane.showInputDialog(this, 
	    			"Invalid directory name.\n" + "" +
	    			"Enter the new directory name:",
	    			"Add Directory", JOptionPane.PLAIN_MESSAGE);
	    	
	    	// check if user cancelled
    		if (dirName == null) return;
    	}
    	
		try {
			this.model.addDirectory(selectedDir.getPath(), dirName);
		}
		catch (Exception e1) {
			JOptionPane.showMessageDialog(
					FileChooser.this, 
					e1.getMessage(), 
					"Could not add directory.", 
					JOptionPane.ERROR_MESSAGE);
		}
	}
	
	/**
	 * Start procedure of uploading a file
	 * Show an open dialog to choose the file to upload
	 * Show a dialog to enter the filename for the remote system
	 */
	private void uploadButton_click() {
		TreePath path = this.jTree.getSelectionPath();
		Directory selectedDir = ((Directory)path.getLastPathComponent());

		JFileChooser openDialog = new JFileChooser();
		
	    int returnVal = openDialog.showOpenDialog(this);
	    
	    if(returnVal == JFileChooser.APPROVE_OPTION) {
	    	String localPath = openDialog.getSelectedFile().getPath();
	    	String localName = openDialog.getSelectedFile().getName();
	    	
	    	String uploadName = JOptionPane.showInputDialog(this, 
	    			"Enter the filename to upload the file as:",
	    			localName);
	    	if (uploadName == null) return;
	    	
	    	// keep asking user until they enter a valid filename
	    	while (!this.model.validateUploadName(uploadName)) {
		    	uploadName = JOptionPane.showInputDialog(this, 
		    			"Invalid filename.\n" + "" +
		    			"Enter the filename to upload the file as:",
		    			localName);
		    	
		    	// check if user cancelled
		    	if (uploadName == null) return;
	    	}
	    	
			try {
				this.model.uploadFile(localPath, 
						selectedDir.getPath(), uploadName);
			}
			catch (Exception e1) {
				JOptionPane.showMessageDialog(this, 
						e1.getMessage(), 
						"Could not upload file.", 
						JOptionPane.ERROR_MESSAGE);
			}
	    }
	}

}
